﻿using System;
using Platformer_Example.Controller.Character.Attack;
using Platformer_Example.Controller.Character.Behavior;
using Platformer_Example.Controller.Data;
using Platformer_Example.Controller.Input;
using Platformer_Example.Controller.Map.Objects;
using Transformable_Engine_v2.Engine.GameObjects.Containers;
using Transformable_Engine_v2.Engine.Global;
using Transformable_Engine_v2.Engine.Transformable_Math.Geometry;

namespace Platformer_Example.Controller.Character
{
    public abstract class BaseInputCharacter : BaseCharacter, IAttacker, IAttackable
    {
        public enum Direction
        {
            None,
            Left,
            Right,
        }

        public enum State
        {
            None,
            Idle,
            Walk,
            Jump,
            Fall,
            Attack,
            LadderClimb,
            LadderIdle,
            Pickup,
            Throw,
            LongThrow,
            AirThrow
        }

        public readonly InputState InputListener;
        protected BaseBehavior Behavior;
        protected BaseThrowable CarriedObject;
        protected Direction CurrentDirection;
        protected State CurrentState;
        public int JumpCount;

        protected BaseInputCharacter(InputState inputState, Team team) : base(team)
        {
            CurrentLadder = null;

            InputListener = inputState;

            InputListener.OnKeyPress += OnKeyPress;
            InputListener.OnKeyRelease += OnKeyRelease;

            CreateIdleStateObjects();
            CreateWalkStateObjects();
            CreateFallStateObjects();
            CreateJumpStateObjects();
            CreateAttackStateObjects();
            CreateLadderClimbStateObjects();
            CreateLadderIdleStateObjects();
            CreatePickupStateObjects();
            CreateThrowStateObjects();
            CreateLongThrowStateObjects();
            CreateAirThrowStateObjects();
        }

        public TE_Rectangle CurrentLadder { get; set; }

        public abstract TE_Vector2 LadderClimbSpeed { get; }
        public abstract TE_Rectangle CollisionLadder { get; }
        protected abstract int JumpLimit { get; }

        protected abstract TE_Vector2 MovementSpeed { get; }

        protected bool CanJump
        {
            get
            {
                bool result = JumpCount < JumpLimit;
                JumpCount++;
                return result;
            }
        }

        public abstract float JumpHeight { get; }
        protected abstract TE_Rectangle CollisionPickup { get; }

        public bool IsCarryingObject
        {
            get { return CarriedObject != null; }
        }

        public abstract bool IsAttackable();
        public abstract bool IsDefending();
        public abstract bool IsReflecting();

        public abstract TE_Rectangle GetHitBoundingBox();
        public abstract void Reflect();

        public Team GetTeam()
        {
            return Team;
        }

        public Direction GetDirection()
        {
            return CurrentDirection;
        }

        public bool InputPressed(InputState.Key key)
        {
            return InputListener.PressKey(key);
        }

        public bool InputReleased(InputState.Key key)
        {
            return InputListener.ReleaseKey(key);
        }

        public void ChangeBehavior(BaseBehavior behavior)
        {
            if (Behavior != null)
            {
                Behavior.Die();
                Behavior.Character = null;
            }

            Behavior = behavior;
            Behavior.Character = this;
            AddChildren(Behavior);
        }

        public void ChangeDirectionFromInput()
        {
            if (InputListener.KeyState[InputState.Key.Left])
                ChangeDirection(Direction.Left);
            else if (InputListener.KeyState[InputState.Key.Right])
                ChangeDirection(Direction.Right);
        }

        public void ChangeWalkSpeedFromInput()
        {
            if (InputListener.KeyState[InputState.Key.Left])
                SetMovement(-MovementSpeed);
            else if (InputListener.KeyState[InputState.Key.Right])
                SetMovement(MovementSpeed);
            else
                ResetMovement();
        }

        private void OnKeyPress(InputState.Key key)
        {
            switch (CurrentState)
            {
                case State.Idle:
                    Behavior.OnKeyPressIdle(key);
                    OnKeyPressIdle(key);
                    break;
                case State.Walk:
                    Behavior.OnKeyPressWalk(key);
                    OnKeyPressWalk(key);
                    break;
                case State.Jump:
                    Behavior.OnKeyPressJump(key);
                    OnKeyPressJump(key);
                    break;
                case State.Fall:
                    Behavior.OnKeyPressFall(key);
                    OnKeyPressFall(key);
                    break;
                case State.Attack:
                    Behavior.OnKeyPressAttack(key);
                    OnKeyPressAttack(key);
                    break;
                case State.LadderIdle:
                    Behavior.OnKeyPressLadderIdle(key);
                    OnKeyPressLadderIdle(key);
                    break;
                case State.LadderClimb:
                    Behavior.OnKeyPressLadderClimb(key);
                    OnKeyPressLadderClimb(key);
                    break;
                case State.Pickup:
                    Behavior.OnKeyPressPickup(key);
                    OnKeyPressPickup(key);
                    break;
                case State.Throw:
                    Behavior.OnKeyPressThrow(key);
                    OnKeyPressThrow(key);
                    break;
                case State.LongThrow:
                    Behavior.OnKeyPressLongThrow(key);
                    OnKeyPressLongThrow(key);
                    break;
                case State.AirThrow:
                    Behavior.OnKeyPressAirThrow(key);
                    OnKeyPressAirThrow(key);
                    break;
            }
        }

        private void OnKeyRelease(InputState.Key key)
        {
            switch (CurrentState)
            {
                case State.Idle:
                    Behavior.OnKeyReleaseIdle(key);
                    OnKeyReleaseIdle(key);
                    break;
                case State.Walk:
                    Behavior.OnKeyReleaseWalk(key);
                    OnKeyReleaseWalk(key);
                    break;
                case State.Jump:
                    Behavior.OnKeyReleaseJump(key);
                    OnKeyReleaseJump(key);
                    break;
                case State.Fall:
                    Behavior.OnKeyReleaseFall(key);
                    OnKeyReleaseFall(key);
                    break;
                case State.Attack:
                    Behavior.OnKeyReleaseAttack(key);
                    OnKeyReleaseAttack(key);
                    break;
                case State.LadderIdle:
                    Behavior.OnKeyReleaseLadderIdle(key);
                    OnKeyReleaseLadderIdle(key);
                    break;
                case State.LadderClimb:
                    Behavior.OnKeyReleaseLadderClimb(key);
                    OnKeyReleaseLadderClimb(key);
                    break;
                case State.Pickup:
                    Behavior.OnKeyReleasePickup(key);
                    OnKeyReleasePickup(key);
                    break;
                case State.Throw:
                    Behavior.OnKeyReleaseThrow(key);
                    OnKeyReleaseThrow(key);
                    break;
                case State.LongThrow:
                    Behavior.OnKeyReleaseLongThrow(key);
                    OnKeyReleaseLongThrow(key);
                    break;
                case State.AirThrow:
                    Behavior.OnKeyReleaseAirThrow(key);
                    OnKeyReleaseAirThrow(key);
                    break;
            }
        }

        protected abstract void OnKeyPressIdle(InputState.Key key);
        protected abstract void OnKeyReleaseIdle(InputState.Key key);

        protected abstract void OnKeyPressWalk(InputState.Key key);
        protected abstract void OnKeyReleaseWalk(InputState.Key key);

        protected abstract void OnKeyPressJump(InputState.Key key);
        protected abstract void OnKeyReleaseJump(InputState.Key key);

        protected abstract void OnKeyPressFall(InputState.Key key);
        protected abstract void OnKeyReleaseFall(InputState.Key key);

        protected abstract void OnKeyPressAttack(InputState.Key key);
        protected abstract void OnKeyReleaseAttack(InputState.Key key);

        protected abstract void OnKeyPressLadderIdle(InputState.Key key);
        protected abstract void OnKeyReleaseLadderIdle(InputState.Key key);

        protected abstract void OnKeyPressLadderClimb(InputState.Key key);
        protected abstract void OnKeyReleaseLadderClimb(InputState.Key key);

        protected abstract void OnKeyPressThrow(InputState.Key key);
        protected abstract void OnKeyReleaseThrow(InputState.Key key);

        protected abstract void OnKeyPressAirThrow(InputState.Key key);
        protected abstract void OnKeyReleaseAirThrow(InputState.Key key);

        protected abstract void OnKeyPressLongThrow(InputState.Key key);
        protected abstract void OnKeyReleaseLongThrow(InputState.Key key);

        protected abstract void OnKeyPressPickup(InputState.Key key);
        protected abstract void OnKeyReleasePickup(InputState.Key key);

        public TE_Rectangle GetLadderCollisionArea()
        {
            return CollisionLadder + AbsolutePosition;
        }

        public TE_Rectangle GetPickupCollisionArea()
        {
            if (CurrentDirection == Direction.Right)
                return CollisionPickup + AbsolutePosition;

            return CollisionPickup.FlipHorizontally + AbsolutePosition;
        }

        protected abstract void CreateIdleStateObjects();
        protected abstract void InitializeIdleState();
        protected abstract void UpdateIdleState(TimeSpan gameTime);
        protected abstract void EndIdleState();

        protected abstract void CreateWalkStateObjects();
        protected abstract void InitializeWalkState();
        protected abstract void UpdateWalkState(TimeSpan gameTime);
        protected abstract void EndWalkState();

        protected abstract void CreateJumpStateObjects();
        protected abstract void InitializeJumpState();
        protected abstract void UpdateJumpState(TimeSpan gameTime);
        protected abstract void EndJumpState();

        protected abstract void CreateFallStateObjects();
        protected abstract void InitializeFallState();
        protected abstract void UpdateFallState(TimeSpan gameTime);
        protected abstract void EndFallState();

        protected abstract void CreateAttackStateObjects();
        protected abstract void InitializeAttackState();
        protected abstract void UpdateAttackState(TimeSpan gameTime);
        protected abstract void EndAttackState();

        protected abstract void CreateLadderClimbStateObjects();
        protected abstract void InitializeLadderClimbState();
        protected abstract void UpdateLadderClimbState(TimeSpan gameTime);
        protected abstract void EndLadderClimbState();

        protected abstract void CreateLadderIdleStateObjects();
        protected abstract void InitializeLadderIdleState();
        protected abstract void UpdateLadderIdleState(TimeSpan gameTime);
        protected abstract void EndLadderIdleState();

        protected abstract void CreateThrowStateObjects();
        protected abstract void InitializeThrowState();
        protected abstract void UpdateThrowState(TimeSpan gameTime);
        protected abstract void EndThrowState();

        protected abstract void CreatePickupStateObjects();
        protected abstract void InitializePickupState();
        protected abstract void UpdatePickupState(TimeSpan gameTime);
        protected abstract void EndPickupState();

        protected abstract void CreateLongThrowStateObjects();
        protected abstract void InitializeLongThrowState();
        protected abstract void UpdateLongThrowState(TimeSpan gameTime);
        protected abstract void EndLongThrowState();

        protected abstract void CreateAirThrowStateObjects();
        protected abstract void InitializeAirThrowState();
        protected abstract void UpdateAirThrowState(TimeSpan gameTime);
        protected abstract void EndAirThrowState();

        protected void ChangeWalkSpeedFromDirection()
        {
            switch (CurrentDirection)
            {
                case Direction.Left:
                    SetMovement(-MovementSpeed);
                    break;
                case Direction.Right:
                    SetMovement(MovementSpeed);
                    break;
            }
        }

        public void ResetJumpCount()
        {
            JumpCount = 0;
        }

        protected void Flip()
        {
            switch (CurrentDirection)
            {
                case Direction.Left:
                    Flip(Direction.Right);
                    break;
                case Direction.Right:
                    Flip(Direction.Left);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        protected void Flip(Direction value)
        {
            if (value != CurrentDirection)
            {
                Character.Mirror = value == Direction.Left
                                       ? ImageContainer.MirrorState.MirrorHorizontal
                                       : ImageContainer.MirrorState.None;
                CurrentDirection = value;
            }
        }

        public override void ChangeMap(string newMap, CharacterPosition newState)
        {
            ChangeState(newState.State);
            ChangeDirection(newState.Direction);

            base.ChangeMap(newMap, newState);
        }

        public void ChangeDirection(Direction newDirection)
        {
            Flip(newDirection);
        }

        public void ChangeState(State newState)
        {
            Log.LogDebug("Change state from " + CurrentState + " to " + newState);
            switch (CurrentState)
            {
                case State.Idle:
                    EndIdleState();
                    break;
                case State.Walk:
                    EndWalkState();
                    break;
                case State.Jump:
                    EndJumpState();
                    break;
                case State.Fall:
                    EndFallState();
                    break;
                case State.Attack:
                    EndAttackState();
                    break;
                case State.LadderClimb:
                    EndLadderClimbState();
                    break;
                case State.LadderIdle:
                    EndLadderIdleState();
                    break;
                case State.Throw:
                    EndThrowState();
                    break;
                case State.LongThrow:
                    EndThrowState();
                    break;
                case State.AirThrow:
                    EndThrowState();
                    break;
                case State.Pickup:
                    EndThrowState();
                    break;
            }

            switch (newState)
            {
                case State.Idle:
                    Behavior.InitializeIdleState();
                    InitializeIdleState();
                    break;
                case State.Walk:
                    Behavior.InitializeWalkState();
                    InitializeWalkState();
                    break;
                case State.Jump:
                    Behavior.InitializeJumpState();
                    InitializeJumpState();
                    break;
                case State.Fall:
                    Behavior.InitializeFallState();
                    InitializeFallState();
                    break;
                case State.Attack:
                    Behavior.InitializeAttackState();
                    InitializeAttackState();
                    break;
                case State.LadderClimb:
                    Behavior.InitializeLadderClimbState();
                    InitializeLadderClimbState();
                    break;
                case State.LadderIdle:
                    Behavior.InitializeLadderIdleState();
                    InitializeLadderIdleState();
                    break;
                case State.AirThrow:
                    Behavior.InitializeAirThrowState();
                    InitializeAirThrowState();
                    break;
                case State.Throw:
                    Behavior.InitializeThrowState();
                    InitializeThrowState();
                    break;
                case State.LongThrow:
                    Behavior.InitializeLongThrowState();
                    InitializeLongThrowState();
                    break;
                case State.Pickup:
                    Behavior.InitializePickupState();
                    InitializePickupState();
                    break;
            }

            CurrentState = newState;
        }

        public override void FixedStepUpdate(TimeSpan gameTime)
        {
            switch (CurrentState)
            {
                case State.Idle:
                    Behavior.UpdateIdleState(gameTime);
                    UpdateIdleState(gameTime);
                    break;
                case State.Walk:
                    Behavior.UpdateWalkState(gameTime);
                    UpdateWalkState(gameTime);
                    break;
                case State.Jump:
                    Behavior.UpdateJumpState(gameTime);
                    UpdateJumpState(gameTime);
                    break;
                case State.Fall:
                    Behavior.UpdateFallState(gameTime);
                    UpdateFallState(gameTime);
                    break;
                case State.Attack:
                    Behavior.UpdateAttackState(gameTime);
                    UpdateAttackState(gameTime);
                    break;
                case State.LadderClimb:
                    Behavior.UpdateLadderClimbState(gameTime);
                    UpdateLadderClimbState(gameTime);
                    break;
                case State.LadderIdle:
                    Behavior.UpdateLadderIdleState(gameTime);
                    UpdateLadderIdleState(gameTime);
                    break;
                case State.Pickup:
                    Behavior.UpdatePickupState(gameTime);
                    UpdatePickupState(gameTime);
                    break;
                case State.Throw:
                    Behavior.UpdateThrowState(gameTime);
                    UpdateThrowState(gameTime);
                    break;
                case State.LongThrow:
                    Behavior.UpdateLongThrowState(gameTime);
                    UpdateLongThrowState(gameTime);
                    break;
                case State.AirThrow:
                    Behavior.UpdateAirThrowState(gameTime);
                    UpdateAirThrowState(gameTime);
                    break;
            }

            base.FixedStepUpdate(gameTime);
        }

        public void PickUpObject(BaseThrowable getThrowableObject)
        {
            CarriedObject = getThrowableObject;
            AddChildren(CarriedObject);
        }
    }
}